package com.ibm.cps.processors;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.ibm.cps.message.MessageFields;
import com.ibm.cps.message.ParsedDataMessage;
import com.ibm.cps.message.ParsedDataMessageBuilder;
import com.ibm.cps.sql.expression.function.meta.FieldNameParam;
import com.ibm.cps.sql.expression.function.meta.IgnoredInFunctionCall;
import com.ibm.cps.sql.expression.function.meta.ValueParam;
import com.ibm.interfaces.ITimeSeriesSchema;
import com.ibm.util.JSONUtility;
import com.ibm.util.exception.CPSException;

/*
 * schema like this.
 *     {
        "processorid": "562729fdda0a6ddb89ae8dfc",
        "parentprocessors": "5627347916d3b38d847a9b18",
        "groupid": "{{groupid}}",
        "type": "dtw",
        "name": "dtw-similarity",
        "functionName": "dtw",
        "target":"b",
        "pattern": [1,2,3,4,5],
        "outputfield": "similarity"
    },
	output
	{
		{"similarity":"double"}
	}
	checked.
 */

@Info(Name=MessageFields.DTW_TYPE,Type=Info.ProcessorType.Vector)
public class DTWProcessor extends SchemaInheritiveProcessor {
	
	@FieldNameParam
	private String inputField;
	@FieldNameParam @IgnoredInFunctionCall
	private String outputField;
	@ValueParam
	private String patternString;
	private int dataIndex;
	private double[] pattern;

	public DTWProcessor(String tenantId, String processorId, String source, ITimeSeriesSchema parentSchema) throws CPSException {
		super(tenantId, processorId, source, parentSchema);
	}

	@Override
	public void dispose() {
	}

	public double dtw(double[] pattern,double[] data)
	{
		
		int plen = pattern.length;
		int data_len = data.length;

		int i,t,k,p,q,end,min_idx,idx,startidx;
		double tmp;
		double mindata;
		double[] tmpval = new double[3];/*0:a, 1: b 2:c*/
		double[][] dist = null;
		int[][] start = null;

		int cur_dist_idx, next_dist_idx, tmp_idx;

		tmp = 0.0;
		
		Matrix2D_double dist_ref = new Matrix2D_double();
		Matrix2D_int start_ref = new Matrix2D_int();
		dist_ref.init_matrix2D_Double(data_len, plen, 0);
		dist_ref.setColumnBySingleValue_Double(0, 1e10);
		start_ref.init_matrix2D_Int(data_len, plen, 0);
		start_ref.setColumnByIncVal_Int(0, 0, 1);
		
		dist = dist_ref.data;
		start = start_ref.data;
		/*init status, first line*/
		t = 0;/*index for data*/
		cur_dist_idx = 0;
		next_dist_idx = 1;
		for( i = 0; i < plen; ++i ){
			tmp += (pattern[i]-data[t])*(pattern[i]-data[t]);
			dist[cur_dist_idx][i] = tmp;
			start[cur_dist_idx][i] = t;
		}
		
		
		/*end of init*/

		for(t = 1; t < data_len; ++t)
		{
			i = 0;
			dist[next_dist_idx][i] = (pattern[0]-data[t])*(pattern[0]-data[t]);
			start[next_dist_idx][i] = t;
			for(i = 1; i < plen; ++i)
			{
				tmpval[0] = dist[cur_dist_idx][(i)];
				tmpval[1] = dist[cur_dist_idx][i-1];
				tmpval[2] = dist[next_dist_idx][i-1];
				min_idx = min3ref(tmpval);
				mindata = tmpval[min_idx];

				dist[next_dist_idx][i] = mindata + (pattern[i]-data[t])*(pattern[i]-data[t]);

				switch(min_idx){
				case 0:{
					start[next_dist_idx][i] = start[cur_dist_idx][(i)];
					break;
				}
				case 1:{
					start[next_dist_idx][i] = start[cur_dist_idx][i-1];
					break;
				}
				case 2:{
					start[next_dist_idx][i] = start[next_dist_idx][i-1];
					break;
				}
				default:{break;}
				}
			}

//			if(dist[next_dist_idx][plen-1] < disthb){
//				/*add the match into result set*/
//				/*new entry found*/
//				/*add it into result set*/
//				startidx = start[next_dist_idx][plen-1];
//				{
//					match_startidx_vec.add(startidx);
//					match_endidx_vec.add(t);
//					matchscorevec.add(dist[next_dist_idx][plen-1]);
////					match_startidxcnt_vec->push_back(match_startidxcnt_vec,startidx);
////					match_startidx_vec->push_back(match_startidx_vec,startidx);
////					match_endidx_vec->push_back(match_endidx_vec,t);
////					matchscorevec->push_back(matchscorevec,dist[next_dist_idx][plen-1]);
//				}
//			}
//			/*swap next_dist_idx and cur_dist_idx*/
//			tmp_idx = next_dist_idx;
//			next_dist_idx = cur_dist_idx;
//			cur_dist_idx = tmp_idx;
			cur_dist_idx++;
			next_dist_idx++;
		}

		return dist[data_len - 1][plen - 1];
	}

	private static int min3ref(double[] vec){
        int i = 0;
        int curidx = 0;
        double curval = 1e10;
        for( i = 0; i < 3; ++i )
        {
                if(curval >= vec[i]){
                        curidx = i;
                        curval = vec[i];
                }
        }
        return curidx;

	}


	@Override
	protected List<NewField> defineFieldsToBeAdded() {
		List<NewField> fields = new ArrayList<>();
		NewField f = new NewField(outputField, "double");
		fields.add(f);
		return fields;
	}

	@Override
	protected Collection<ParsedDataMessage> runProcessorLogic(ITimeSeriesSchema parentSchema,
			ITimeSeriesSchema outputSchema, ParsedDataMessage message) throws CPSException {
		Collection<ParsedDataMessage> messages = new ArrayList<>();
		ParsedDataMessageBuilder outputBuilder = ParsedDataMessageBuilder.createInhertiveBuilder(outputSchema, message, parentSchema);

//		Object[] data = message.getArrayValue(dataIndex);
//		double[] _data = new double[data.length];
//		for (int i = 0; i < data.length; i++) {
//			_data[i] = (double) data[i];
//		}
		Double[] _data = message.getArray(dataIndex);
		
		double result = dtw(pattern, ArrayUtils.toPrimitive(_data));
		
		outputBuilder.append(outputField, result);
		messages.add(outputBuilder.toParsedDataMessage());
		return messages;
	}

	@Override
	public void initialize() throws CPSException {
		dataIndex = parentSchema.getTagId(inputField);
		ArrayNode patterns = (ArrayNode)JSONUtility.fromString(patternString);
		pattern = new double[patterns.size()];
		for (int i = 0; i < patterns.size(); i++) {
			pattern[i] = patterns.get(i).asDouble();
		}
	}

}

class Matrix2D_double {

	int line;
	int column;
	double[][] data;
	int[] col_idx;
	
	public static void main(String[] args) {

	}
	
	/*matrix utilities*/
	/*make 2-d matrix*/
	/**
	 *line: number of rows
	 *row: number of columns
	 */

	public void init_matrix2D_Double(int line, int col, double default_val)
	{
		int i = 0;
		int j = 0;
		data = null;

		this.line = line;
		this.column = col;

		data = new double[line][col];

		for (i = 0; i < line; i++)
		{
		   for(j = 0; j < col; j++)
		   {
			   data[i][j] = default_val;
		   }
		}
		
		col_idx =  new int[col];
		for(j = 0; j < col; j++)
		{
			col_idx[j] = j;
		}

	}
	
	public void setColumnBySingleValue_Double(int column_idx, double value){
		int i;
		int[] cidxs =  col_idx;
		for (i = 0; i < line; i++)
		{
			data[i][cidxs[column_idx]] = value;
		}
	}
	
	void print_2DMatrix_Double()
	{
		int i,j,tmp;

		System.out.println("print_2DMatrix_Double\n");
		System.out.println("col_idx_Vector::\n");
		for(j = 0; j < column; ++j)
		{
			tmp = col_idx[j];
			System.out.print(tmp+" ");
		}
		System.out.println("\n");

		for(i = 0; i < line; ++i)
		{
		for(j = 0; j < column; ++j)
			{
				System.out.print(data[i][col_idx[j]] + " ");
			}
		System.out.println("\n");
		}
	}
	
}

class Matrix2D_int {

	int line;
	int column;
	int[][] data;
	int[] col_idx;
	
	public static void main(String[] args) {

	}

	public void init_matrix2D_Int(int line, int col, int default_val)
	{
		int i = 0;
		int j = 0;

		this.line = line;
		this.column = col;

		data = new int[line][col];

		for (i = 0; i < line; i++)
		{
		   for(j = 0; j < col; j++)
		   {
			   data[i][j] = default_val;
		   }
		}

		col_idx =  new int[col];
		for(j = 0; j < col; j++)
		{
			col_idx[j] = j;
		}

		return;
	}
	
	
	public void setColumnByIncVal_Int(int col_idx, int begvalue, int delta){
		int i,curval;
		curval = begvalue;

		for (i = 0; i < column; i++)
		{
			data[col_idx][i] = curval;
			curval += delta;
		}
	}
	
	public void print_2DMatrix_Int()
	{
		int i,j;

		System.out.println("print_2DMatrix_Int\n");
		System.out.println("col_idx_Vector::\n");
		for(j = 0; j < column; ++j)
		{
				System.out.print(j+" ");
		}
		System.out.println("\n");

		for(i = 0; i < line; ++i)
		{
		for(j = 0; j < column; ++j)
			{
				System.out.print(data[i][col_idx[j]] + " ");
			}
		System.out.println("\n");
		}
	}
	
}
